package org.apache.ocean.main.search;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DisjunctionMaxQuery;
import org.apache.lucene.search.Query;
import org.apache.ocean.main.IndexSchema;
import org.apache.ocean.main.client.DisMaxClient;
import org.apache.ocean.main.util.QueryUtil;
import org.apache.ocean.main.util.Timeout;

public class DisjunctionMaxQueryParser {
	private static String IMPOSSIBLE_FIELD_NAME = "\uFFFC\uFFFC\uFFFC";
	private Query result;

	private DisjunctionMaxQueryParser(DisMaxClient.Query clientQuery, IndexSchema.Category schemaCategory, Timeout timeout) throws ParseException {
		BooleanQuery query = new BooleanQuery(true);
		Parser userQueryParser = new Parser(IMPOSSIBLE_FIELD_NAME, schemaCategory, timeout);
		userQueryParser.addAlias(IMPOSSIBLE_FIELD_NAME, clientQuery.getTieBreaker(), clientQuery.getUserFields());
		userQueryParser.setPhraseSlop(clientQuery.getQuerySlop());
		Query userQuery = userQueryParser.parse(clientQuery.getQuery());
		query.add(userQuery, BooleanClause.Occur.MUST);
		if (userQuery instanceof BooleanQuery) {
      BooleanQuery booleanQuery = new BooleanQuery();
      QueryUtil.flattenBooleanQuery(booleanQuery, (BooleanQuery)userQuery);
      QueryUtil.setMinShouldMatch(booleanQuery, clientQuery.getMinShouldMatch());
      userQuery = booleanQuery;
    }
		Parser phraseQueryParser = new Parser(IMPOSSIBLE_FIELD_NAME, schemaCategory, timeout);
		phraseQueryParser.addAlias(IMPOSSIBLE_FIELD_NAME, clientQuery.getTieBreaker(), clientQuery.getPhraseFields());
		phraseQueryParser.setPhraseSlop(clientQuery.getPhraseSlop());
		String userPhraseQuery = clientQuery.getQuery().replace("\"", "");
		Query phraseQuery = phraseQueryParser.parse("\"" + userPhraseQuery + "\"");
		if (phraseQuery != null) {
			query.add(phraseQuery, BooleanClause.Occur.SHOULD);
		}
		List<Query> boostQueries = new ArrayList<Query>();
		for (String queryString : clientQuery.getBoostQueries()) {
			boostQueries.add(OceanQueryParser.parse(queryString, schemaCategory));
		}
		if (boostQueries.size() > 0) {
			for(Query boostQuery : boostQueries) {
        query.add(boostQuery, BooleanClause.Occur.SHOULD);
      }
    }
		result = query;
	}

	public Query getQuery() {
		return result;
	}

	public static Query parse(DisMaxClient.Query clientQuery, IndexSchema.Category schemaCategory, Timeout timeout) throws ParseException {
		DisjunctionMaxQueryParser disjunctionMaxQueryParser = new DisjunctionMaxQueryParser(clientQuery, schemaCategory, timeout);
		return disjunctionMaxQueryParser.getQuery();
	}

	public static class Parser extends OceanQueryParser {
		/**
		 * A simple container for storing alias info
		 * 
		 * @see #aliases
		 */
		protected static class Alias {
			public float tie;
			public Map<String, Float> fields;
		}

		/**
		 * Where we store a map from field name we expect to see in our query
		 * string, to Alias object containing the fields to use in our
		 * DisjunctionMaxQuery and the tiebreaker to use.
		 */
		protected Map<String, Alias> aliases = new HashMap<String, Alias>(3);

		public Parser(String defaultField, IndexSchema.Category schemaCategory, Timeout timeout) {
			super(defaultField, schemaCategory, null, timeout);
			// don't trust that our parent class won't ever change it's default
			setDefaultOperator(QueryParser.Operator.OR);
		}

		/**
		 * Add an alias to this query parser.
		 * 
		 * @param field
		 *          the field name that should trigger alias mapping
		 * @param fieldBoosts
		 *          the mapping from fieldname to boost value that should be used to
		 *          build up the clauses of the DisjunctionMaxQuery.
		 * @param tiebreaker
		 *          to the tiebreaker to be used in the DisjunctionMaxQuery
		 * 
		 */
		public void addAlias(String field, float tiebreaker, Map<String, Float> fieldBoosts) {
			Alias a = new Alias();
			a.tie = tiebreaker;
			a.fields = fieldBoosts;
			aliases.put(field, a);
		}

		/**
		 * Delegates to the super class unless the field has been specified as an
		 * alias -- in which case we recurse on each of the aliased fields, and the
		 * results are composed into a DisjunctionMaxQuery. (aliases which
		 * point at other aliases should work)
		 */
		protected Query getFieldQuery(String field, String queryText) throws ParseException {
			if (aliases.containsKey(field)) {
				Alias alias = aliases.get(field);
				DisjunctionMaxQuery q = new DisjunctionMaxQuery(alias.tie);
				/*
				 * we might not get any valid queries from delegation, in which we
				 * should return null
				 */
				boolean ok = false;
				for (String f : alias.fields.keySet()) {
					Query subQuery = getFieldQuery(f, queryText);
					if (null != subQuery) {
						if (alias.fields.get(f) != null) {
							subQuery.setBoost(alias.fields.get(f));
						}
						q.add(subQuery);
						ok = true;
					}
				}
				return ok ? q : null;
			} else {
				return super.getFieldQuery(field, queryText);
			}
		}
	}
}
